home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-02 / comp-pas.zip / TUTORPAS.ZIP / TUTOR8.DOC < prev    next >
Text File  |  1989-05-21  |  27KB  |  607 lines

  1. OPA A
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  
  27.  
  28.                             LET'S BUILD A COMPILER!
  29.  
  30.                                        By
  31.  
  32.                             Jack W. Crenshaw, Ph.D.
  33.  
  34.                                   2 April 1989
  35.  
  36.  
  37.                          Part VIII: A LITTLE PHILOSOPHY
  38.  
  39.  
  40.  
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67. PA A
  68.  
  69.  
  70.  
  71.  
  72.  
  73.        *****************************************************************
  74.        *                                                               *
  75.        *                        COPYRIGHT NOTICE                       *
  76.        *                                                               *
  77.        *   Copyright (C) 1989 Jack W. Crenshaw. All rights reserved.   *
  78.        *                                                               *
  79.        *****************************************************************
  80.  
  81.  
  82.        INTRODUCTION
  83.  
  84.        This is going to be a  different  kind of session than the others
  85.        in our series on  parsing  and  compiler  construction.  For this
  86.        session, there won't be  any  experiments to do or code to write.
  87.        This  once,  I'd  like  to  just  talk  with  you  for  a  while.
  88.        Mercifully, it will be a short  session,  and then we can take up
  89.        where we left off, hopefully with renewed vigor.
  90.  
  91.        When  I  was  in college, I found that I could  always  follow  a
  92.        prof's lecture a lot better if I knew where he was going with it.
  93.        I'll bet you were the same.
  94.  
  95.        So I thought maybe it's about  time  I told you where we're going
  96.        with this series: what's coming up in future installments, and in
  97.        general what all  this  is  about.   I'll also share some general
  98.        thoughts concerning the usefulness of what we've been doing.
  99.  
  100.  
  101.        THE ROAD HOME
  102.  
  103.        So far, we've  covered  the parsing and translation of arithmetic
  104.        expressions,  Boolean expressions, and combinations connected  by
  105.        relational  operators.    We've also done the  same  for  control
  106.        constructs.    In  all of this we've leaned heavily on the use of
  107.        top-down, recursive  descent  parsing,  BNF  definitions  of  the
  108.        syntax, and direct generation of assembly-language code.  We also
  109.        learned the value of  such  tricks  as single-character tokens to
  110.        help  us  see  the  forest  through  the  trees.    In  the  last
  111.        installment  we dealt with lexical scanning,  and  I  showed  you
  112.        simple but powerful ways to remove the single-character barriers.
  113.  
  114.        Throughout the whole study, I've emphasized  the  KISS philosophy
  115.        ... Keep It Simple, Sidney ... and I hope by now  you've realized
  116.        just  how  simple  this stuff can really be.  While there are for
  117.        sure areas of compiler  theory  that  are truly intimidating, the
  118.        ultimate message of this series is that in practice you  can just
  119.        politely  sidestep   many  of  these  areas.    If  the  language
  120.        definition  cooperates  or,  as in this series, if you can define
  121.        the language as you go, it's possible to write down  the language
  122.        definition in BNF with reasonable ease.  And, as we've  seen, you
  123.        can crank out parse procedures from the BNF just about as fast as
  124.        you can type.ABAB
  125.                                      - 2 -A*A*
  126. PA A
  127.  
  128.  
  129.  
  130.  
  131.  
  132.        As our compiler has taken form, it's gotten more parts,  but each
  133.        part  is  quite small and simple, and  very  much  like  all  the
  134.        others.
  135.  
  136.        At this point, we have many  of  the makings of a real, practical
  137.        compiler.  As a matter of  fact,  we  already have all we need to
  138.        build a toy  compiler  for  a  language as powerful as, say, Tiny
  139.        BASIC.  In the next couple of installments, we'll  go  ahead  and
  140.        define that language.
  141.  
  142.        To round out  the  series,  we  still  have a few items to cover.
  143.        These include:
  144.  
  145.           o Procedure calls, with and without parameters
  146.  
  147.           o Local and global variables
  148.  
  149.           o Basic types, such as character and integer types
  150.  
  151.           o Arrays
  152.  
  153.           o Strings
  154.  
  155.           o User-defined types and structures
  156.  
  157.           o Tree-structured parsers and intermediate languages
  158.  
  159.           o Optimization
  160.  
  161.        These will all be  covered  in  future  installments.  When we're
  162.        finished, you'll have all the tools you need to design  and build
  163.        your own languages, and the compilers to translate them.
  164.  
  165.        I can't  design  those  languages  for  you,  but I can make some
  166.        comments  and  recommendations.    I've  already  sprinkled  some
  167.        throughout past installments.    You've  seen,  for  example, the
  168.        control constructs I prefer.
  169.  
  170.        These constructs are going  to  be part of the languages I build.
  171.        I  have  three  languages in mind at this point, two of which you
  172.        will see in installments to come:
  173.  
  174.        TINY - A  minimal,  but  usable  language  on the order  of  Tiny
  175.               BASIC or Tiny C.  It won't be very practical, but  it will
  176.               have enough power to let you write and  run  real programs
  177.               that do something worthwhile.
  178.  
  179.        KISS - The  language  I'm  building for my  own  use.    KISS  is
  180.               intended to be  a  systems programming language.  It won't
  181.               have strong typing  or  fancy data structures, but it will
  182.               support most of  the  things  I  want to do with a higher-
  183.               order language (HOL), except perhaps writing compilers.ABAB
  184.                                      - 3 -A*A*
  185. PA A
  186.  
  187.  
  188.  
  189.  
  190.  
  191.        I've also  been  toying  for  years  with  the idea of a HOL-like
  192.        assembler,  with  structured  control  constructs   and  HOL-like
  193.        assignment statements.  That, in  fact, was the impetus behind my
  194.        original foray into the jungles of compiler theory.  This one may
  195.        never be built, simply  because  I've  learned that it's actually
  196.        easier to implement a language like KISS, that only uses a subset
  197.        of the CPU instructions.    As you know, assembly language can be
  198.        bizarre  and  irregular  in the extreme, and a language that maps
  199.        one-for-one onto it can be a real challenge.  Still,  I've always
  200.        felt that the syntax used  in conventional assemblers is dumb ...
  201.        why is
  202.  
  203.             MOVE.L A,B
  204.  
  205.        better, or easier to translate, than
  206.  
  207.             B=A ?
  208.  
  209.        I  think  it  would  be  an  interesting  exercise to  develop  a
  210.        "compiler" that  would give the programmer complete access to and
  211.        control over the full complement  of the CPU instruction set, and
  212.        would allow you to generate  programs  as  efficient  as assembly
  213.        language, without the pain  of  learning a set of mnemonics.  Can
  214.        it be done?  I don't  know.  The  real question may be, "Will the
  215.        resulting language be any  easier  to  write  than assembly"?  If
  216.        not, there's no point in it.  I think that it  can  be  done, but
  217.        I'm not completely sure yet how the syntax should look.
  218.  
  219.        Perhaps you have some  comments  or suggestions on this one.  I'd
  220.        love to hear them.
  221.  
  222.        You probably won't be surprised to learn that I've already worked
  223.        ahead in most  of the areas that we will cover.  I have some good
  224.        news:  Things  never  get  much  harder than they've been so far.
  225.        It's  possible  to  build a complete, working compiler for a real
  226.        language, using nothing  but  the same kinds of techniques you've
  227.        learned so far.  And THAT brings up some interesting questions.
  228.  
  229.  
  230.        WHY IS IT SO SIMPLE?
  231.  
  232.        Before embarking  on this series, I always thought that compilers
  233.        were just naturally complex computer  programs  ...  the ultimate
  234.        challenge.  Yet the things we have done here have  usually turned
  235.        out to be quite simple, sometimes even trivial.
  236.  
  237.        For awhile, I thought  is  was simply because I hadn't yet gotten
  238.        into the meat  of  the  subject.    I had only covered the simple
  239.        parts.  I will freely admit  to  you  that, even when I began the
  240.        series,  I  wasn't  sure how far we would be able  to  go  before
  241.        things got too complex to deal with in the ways  we  have so far.
  242.        But at this point I've already  been  down the road far enough to
  243.        see the end of it.  Guess what?A6A6
  244.                                      - 4 -A*A*
  245. PA A
  246.  
  247.  
  248.  
  249.  
  250.  
  251.                             THERE ARE NO HARD PARTS!
  252.  
  253.  
  254.        Then, I thought maybe it was because we were not  generating very
  255.        good object  code.    Those  of  you  who have been following the
  256.        series and trying sample compiles know that, while the code works
  257.        and  is  rather  foolproof,  its  efficiency is pretty awful.   I
  258.        figured that if we were  concentrating on turning out tight code,
  259.        we would soon find all that missing complexity.
  260.  
  261.        To  some  extent,  that one is true.  In particular, my first few
  262.        efforts at trying to improve efficiency introduced  complexity at
  263.        an alarming rate.  But since then I've been tinkering around with
  264.        some simple optimizations and I've found some that result in very
  265.        respectable code quality, WITHOUT adding a lot of complexity.
  266.  
  267.        Finally, I thought that  perhaps  the  saving  grace was the "toy
  268.        compiler" nature of the study.   I  have made no pretense that we
  269.        were  ever  going  to be able to build a compiler to compete with
  270.        Borland and Microsoft.  And yet, again, as I get deeper into this
  271.        thing the differences are starting to fade away.
  272.  
  273.        Just  to make sure you get the message here, let me state it flat
  274.        out:
  275.  
  276.           USING THE TECHNIQUES WE'VE USED  HERE,  IT  IS  POSSIBLE TO
  277.           BUILD A PRODUCTION-QUALITY, WORKING COMPILER WITHOUT ADDING
  278.           A LOT OF COMPLEXITY TO WHAT WE'VE ALREADY DONE.
  279.  
  280.  
  281.        Since  the series began I've received  some  comments  from  you.
  282.        Most of them echo my own thoughts:  "This is easy!    Why  do the
  283.        textbooks make it seem so hard?"  Good question.
  284.  
  285.        Recently, I've gone back and looked at some of those texts again,
  286.        and even bought and read some new ones.  Each  time,  I come away
  287.        with the same feeling: These guys have made it seem too hard.
  288.  
  289.        What's going on here?  Why does the whole thing seem difficult in
  290.        the texts, but easy to us?    Are  we that much smarter than Aho,
  291.        Ullman, Brinch Hansen, and all the rest?
  292.  
  293.        Hardly.  But we  are  doing some things differently, and more and
  294.        more  I'm  starting  to appreciate the value of our approach, and
  295.        the way that  it  simplifies  things.    Aside  from  the obvious
  296.        shortcuts that I outlined in Part I, like single-character tokens
  297.        and console I/O, we have  made some implicit assumptions and done
  298.        some things differently from those who have designed compilers in
  299.        the past. As it turns out, our approach makes life a lot easier.
  300.  
  301.        So why didn't all those other guys use it?
  302.  
  303.        You have to remember the context of some of the  earlier compiler
  304.        development.  These people were working with very small computersA*A*
  305.                                      - 5 -
  306. PA A
  307.  
  308.  
  309.  
  310.  
  311.  
  312.        of  limited  capacity.      Memory  was  very  limited,  the  CPU
  313.        instruction  set  was  minimal, and programs ran  in  batch  mode
  314.        rather  than  interactively.   As it turns out, these caused some
  315.        key design decisions that have  really  complicated  the designs.
  316.        Until recently,  I hadn't realized how much of classical compiler
  317.        design was driven by the available hardware.
  318.  
  319.        Even in cases where these  limitations  no  longer  apply, people
  320.        have  tended  to  structure their programs in the same way, since
  321.        that is the way they were taught to do it.
  322.  
  323.        In  our case, we have started with a blank sheet of paper.  There
  324.        is a danger there, of course,  that  you will end up falling into
  325.        traps that other people have long since learned to avoid.  But it
  326.        also has allowed us to  take different approaches that, partly by
  327.        design  and partly by pure dumb luck, have  allowed  us  to  gain
  328.        simplicity.
  329.  
  330.        Here are the areas that I think have  led  to  complexity  in the
  331.        past:
  332.  
  333.          o  Limited RAM Forcing Multiple Passes
  334.  
  335.             I  just  read  "Brinch  Hansen  on  Pascal   Compilers"  (an
  336.             excellent book, BTW).  He  developed a Pascal compiler for a
  337.             PC, but he started the effort in 1981 with a 64K system, and
  338.             so almost every design decision  he made was aimed at making
  339.             the compiler fit  into  RAM.    To do this, his compiler has
  340.             three passes, one of which is the lexical scanner.  There is
  341.             no way he could, for  example, use the distributed scanner I
  342.             introduced  in  the last installment,  because  the  program
  343.             structure wouldn't allow it.  He also required  not  one but
  344.             two intermediate  languages,  to  provide  the communication
  345.             between phases.
  346.  
  347.             All the early compiler writers  had to deal with this issue:
  348.             Break the compiler up into enough parts so that it  will fit
  349.             in memory.  When  you  have multiple passes, you need to add
  350.             data structures to support the  information  that  each pass
  351.             leaves behind for the next.   That adds complexity, and ends
  352.             up driving the  design.    Lee's  book,  "The  Anatomy  of a
  353.             Compiler,"  mentions a FORTRAN compiler developed for an IBM
  354.             1401.  It had no fewer than 63 separate passes!  Needless to
  355.             say,  in a compiler like this  the  separation  into  phases
  356.             would dominate the design.
  357.  
  358.             Even in  situations  where  RAM  is  plentiful,  people have
  359.             tended  to  use  the same techniques because  that  is  what
  360.             they're familiar with.   It  wasn't  until Turbo Pascal came
  361.             along that we found how simple a compiler could  be  if  you
  362.             started with different assumptions.
  363.  
  364.  
  365.          o  Batch ProcessingA*A*
  366.                                      - 6 -
  367. PA A
  368.  
  369.  
  370.  
  371.  
  372.  
  373.             In the early days, batch  processing was the only choice ...
  374.             there was no interactive computing.   Even  today, compilers
  375.             run in essentially batch mode.
  376.  
  377.             In a mainframe compiler as  well  as  many  micro compilers,
  378.             considerable effort is expended on error recovery ... it can
  379.             consume as much as 30-40%  of  the  compiler  and completely
  380.             drive the design.  The idea is to avoid halting on the first
  381.             error, but rather to keep going at all costs,  so  that  you
  382.             can  tell  the  programmer about as many errors in the whole
  383.             program as possible.
  384.  
  385.             All of that harks back to the days of the  early mainframes,
  386.             where turnaround time was measured  in hours or days, and it
  387.             was important to squeeze every last ounce of information out
  388.             of each run.
  389.  
  390.             In this series, I've been very careful to avoid the issue of
  391.             error recovery, and instead our compiler  simply  halts with
  392.             an error message on  the  first error.  I will frankly admit
  393.             that it was mostly because I wanted to take the easy way out
  394.             and keep things simple.   But  this  approach,  pioneered by
  395.             Borland in Turbo Pascal, also has a lot going for it anyway.
  396.             Aside from keeping the  compiler  simple,  it also fits very
  397.             well  with   the  idea  of  an  interactive  system.    When
  398.             compilation is  fast, and especially when you have an editor
  399.             such as Borland's that  will  take you right to the point of
  400.             the error, then it makes a  lot  of sense to stop there, and
  401.             just restart the compilation after the error is fixed.
  402.  
  403.  
  404.          o  Large Programs
  405.  
  406.             Early compilers were designed to handle  large  programs ...
  407.             essentially infinite ones.    In those days there was little
  408.             choice;  the  idea  of  subroutine  libraries  and  separate
  409.             compilation  were  still  in  the  future.      Again,  this
  410.             assumption led to  multi-pass designs and intermediate files
  411.             to hold the results of partial processing.
  412.  
  413.             Brinch Hansen's  stated goal was that the compiler should be
  414.             able to compile itself.   Again, because of his limited RAM,
  415.             this drove him to a multi-pass design.  He needed  as little
  416.             resident compiler code as possible,  so  that  the necessary
  417.             tables and other data structures would fit into RAM.
  418.  
  419.             I haven't stated this one yet, because there  hasn't  been a
  420.             need  ... we've always just read and  written  the  data  as
  421.             streams, anyway.  But  for  the  record,  my plan has always
  422.             been that, in  a  production compiler, the source and object
  423.             data should all coexist  in  RAM with the compiler, a la the
  424.             early Turbo Pascals.  That's why I've been  careful  to keep
  425.             routines like GetChar  and  Emit  as  separate  routines, inA6A6
  426.                                      - 7 -A*A*
  427. PA A
  428.  
  429.  
  430.  
  431.  
  432.  
  433.             spite of their small size.   It  will be easy to change them
  434.             to read to and write from memory.
  435.  
  436.  
  437.          o  Emphasis on Efficiency
  438.  
  439.             John  Backus has stated that, when  he  and  his  colleagues
  440.             developed the original FORTRAN compiler, they KNEW that they
  441.             had to make it produce tight code.  In those days, there was
  442.             a strong sentiment against HOLs  and  in  favor  of assembly
  443.             language, and  efficiency was the reason.  If FORTRAN didn't
  444.             produce very good  code  by  assembly  standards,  the users
  445.             would simply refuse to use it.  For the record, that FORTRAN
  446.             compiler turned out to  be  one  of  the most efficient ever
  447.             built, in terms of code quality.  But it WAS complex!
  448.  
  449.             Today,  we have CPU power and RAM size  to  spare,  so  code
  450.             efficiency is not  so  much  of  an  issue.    By studiously
  451.             ignoring this issue, we  have  indeed  been  able to Keep It
  452.             Simple.    Ironically,  though, as I have said, I have found
  453.             some optimizations that we can  add  to  the  basic compiler
  454.             structure, without having to add a lot of complexity.  So in
  455.             this  case we get to have our cake and eat it too:  we  will
  456.             end up with reasonable code quality, anyway.
  457.  
  458.  
  459.          o  Limited Instruction Sets
  460.  
  461.             The early computers had primitive instruction sets.   Things
  462.             that  we  take  for granted, such as  stack  operations  and
  463.             indirect addressing, came only with great difficulty.
  464.  
  465.             Example: In most compiler designs, there is a data structure
  466.             called the literal pool.  The compiler  typically identifies
  467.             all literals used in the program, and collects  them  into a
  468.             single data structure.    All references to the literals are
  469.             done  indirectly  to  this  pool.    At  the   end   of  the
  470.             compilation, the  compiler  issues  commands  to  set  aside
  471.             storage and initialize the literal pool.
  472.  
  473.             We haven't had to address that  issue  at all.  When we want
  474.             to load a literal, we just do it, in line, as in
  475.  
  476.                  MOVE #3,D0
  477.  
  478.             There is something to be said for the use of a literal pool,
  479.             particularly on a machine like  the 8086 where data and code
  480.             can  be separated.  Still, the whole  thing  adds  a  fairly
  481.             large amount of complexity with little in return.
  482.  
  483.             Of course, without the stack we would be lost.  In  a micro,
  484.             both  subroutine calls and temporary storage depend  heavily
  485.             on the stack, and  we  have used it even more than necessary
  486.             to ease expression parsing.A*A*
  487.                                      - 8 -
  488. PA A
  489.  
  490.  
  491.  
  492.  
  493.  
  494.          o  Desire for Generality
  495.  
  496.             Much of the content of the typical compiler text is taken up
  497.             with issues we haven't addressed here at all ... things like
  498.             automated  translation  of  grammars,  or generation of LALR
  499.             parse tables.  This is not simply because  the  authors want
  500.             to impress you.  There are good, practical  reasons  why the
  501.             subjects are there.
  502.  
  503.             We have been concentrating on the use of a recursive-descent
  504.             parser to parse a  deterministic  grammar,  i.e.,  a grammar
  505.             that is not ambiguous and, therefore, can be parsed with one
  506.             level of lookahead.  I haven't made much of this limitation,
  507.             but  the  fact  is  that  this represents a small subset  of
  508.             possible grammars.  In fact,  there is an infinite number of
  509.             grammars that we can't parse using our techniques.    The LR
  510.             technique is a more powerful one, and can deal with grammars
  511.             that we can't.
  512.  
  513.             In compiler theory, it's important  to know how to deal with
  514.             these  other  grammars,  and  how  to  transform  them  into
  515.             grammars  that  are  easier to deal with.  For example, many
  516.             (but not all) ambiguous  grammars  can  be  transformed into
  517.             unambiguous ones.  The way to do this is not always obvious,
  518.             though, and so many people  have  devoted  years  to develop
  519.             ways to transform them automatically.
  520.  
  521.             In practice, these  issues  turn out to be considerably less
  522.             important.  Modern languages tend  to be designed to be easy
  523.             to parse, anyway.   That  was a key motivation in the design
  524.             of Pascal.   Sure,  there are pathological grammars that you
  525.             would be hard pressed to write unambiguous BNF  for,  but in
  526.             the  real  world  the best answer is probably to avoid those
  527.             grammars!
  528.  
  529.             In  our  case,  of course, we have sneakily let the language
  530.             evolve  as  we  go, so we haven't painted ourselves into any
  531.             corners here.  You may not always have that luxury.   Still,
  532.             with a little  care  you  should  be able to keep the parser
  533.             simple without having to resort to automatic  translation of
  534.             the grammar.
  535.  
  536.  
  537.        We have taken  a  vastly  different  approach in this series.  We
  538.        started with a clean sheet  of  paper,  and  developed techniques
  539.        that work in the context that  we  are in; that is, a single-user
  540.        PC  with  rather  ample CPU power and RAM space.  We have limited
  541.        ourselves to reasonable grammars that  are easy to parse, we have
  542.        used the instruction set of the CPU to advantage, and we have not
  543.        concerned ourselves with efficiency.  THAT's why it's been easy.
  544.  
  545.        Does this mean that we are forever doomed  to  be  able  to build
  546.        only toy compilers?   No, I don't think so.  As I've said, we can
  547.        add  certain   optimizations   without   changing   the  compilerA*A*
  548.                                      - 9 -
  549. PA A
  550.  
  551.  
  552.  
  553.  
  554.  
  555.        structure.  If we want to process large files, we can  always add
  556.        file  buffering  to do that.  These  things  do  not  affect  the
  557.        overall program design.
  558.  
  559.        And I think  that's  a  key  factor.   By starting with small and
  560.        limited  cases,  we  have been able to concentrate on a structure
  561.        for  the  compiler  that is natural  for  the  job.    Since  the
  562.        structure naturally fits the job, it is almost bound to be simple
  563.        and transparent.   Adding  capability doesn't have to change that
  564.        basic  structure.    We  can  simply expand things like the  file
  565.        structure or add an optimization layer.  I guess  my  feeling  is
  566.        that, back when resources were tight, the structures people ended
  567.        up  with  were  artificially warped to make them work under those
  568.        conditions, and weren't optimum  structures  for  the  problem at
  569.        hand.
  570.  
  571.  
  572.        CONCLUSION
  573.  
  574.        Anyway, that's my arm-waving  guess  as to how we've been able to
  575.        keep things simple.  We started with something simple and  let it
  576.        evolve  naturally,  without  trying  to   force   it   into  some
  577.        traditional mold.
  578.  
  579.        We're going to  press on with this.  I've given you a list of the
  580.        areas  we'll  be  covering in future installments.    With  those
  581.        installments, you  should  be  able  to  build  complete, working
  582.        compilers for just about any occasion, and build them simply.  If
  583.        you REALLY want to build production-quality compilers,  you'll be
  584.        able to do that, too.
  585.  
  586.        For those of you who are chafing at the bit for more parser code,
  587.        I apologize for this digression.  I just thought  you'd  like  to
  588.        have things put  into  perspective  a  bit.  Next time, we'll get
  589.        back to the mainstream of the tutorial.
  590.  
  591.        So far, we've only looked at pieces of compilers,  and  while  we
  592.        have  many  of  the  makings  of a complete language, we  haven't
  593.        talked about how to put  it  all  together.    That  will  be the
  594.        subject of our next  two  installments.  Then we'll press on into
  595.        the new subjects I listed at the beginning of this installment.
  596.  
  597.        See you then.
  598.  
  599.        *****************************************************************
  600.        *                                                               *
  601.        *                        COPYRIGHT NOTICE                       *
  602.        *                                                               *
  603.        *   Copyright (C) 1989 Jack W. Crenshaw. All rights reserved.   *
  604.        *                                                               *
  605.        *****************************************************************ANAN
  606.                                     - 10 -A*A*
  607. @